其他
摆脱剧荒!教你用 Python 一步步爬取豆瓣电影新榜单
以下文章来源于数据不吹牛 ,作者吹牛Z
本文以豆瓣电影(非TOP250)为例,从数据爬取、清洗与分析三个维度入手,详解和还原数据爬取到分析的全链路。
作者 | 周志鹏
责编 | 郭 芮
headers = {'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'}
def parse_base_info(url,headers):
html = requests.get(url,headers = headers)
bs = json.loads(html.text)
df = pd.DataFrame()
for i in bs['data']:
casts = i['casts'] #主演
cover = i['cover'] #海报
directors = i['directors'] #导演
m_id = i['id'] #ID
rate = i['rate'] #评分
star = i['star'] #标记人数
title = i['title'] #片名
url = i['url'] #网址
cache = pd.DataFrame({'主演':[casts],'海报':[cover],'导演':[directors],
'ID':[m_id],'评分':[rate],'标记':[star],'片名':[title],'网址':[url]})
df = pd.concat([df,cache])
return df
#你想爬取多少页,其实这里对应着加载多少次
def format_url(num):
urls = []
base_url = 'https://movie.douban.com/j/new_search_subjects?sort=T&range=0,10&tags=%E7%94%B5%E5%BD%B1&start={}'
for i in range(0,20 * num,20):
url = base_url.format(i)
urls.append(url)
return urls
urls = format_url(450)
result = pd.DataFrame()
#看爬取了多少页
count = 1
for url in urls:
df = parse_base_info(url,headers = headers)
result = pd.concat([result,df])
time.sleep(random.random() + 2)
print('I had crawled page of:%d' % count)
count += 1
一个大号的功夫,包含电影ID、电影名称、主演、导演、评分、标记人数和具体网址的数据已经爬好了:
def parse_movie_info(url,headers = headers,ip = ''):
if ip == '':
html = requests.get(url,headers = headers)
else:
html = requests.get(url,headers = headers,proxies = ip)
bs = etree.HTML(html.text)
#片名
title = bs.xpath('//div[@id = "wrapper"]/div/h1/span')[0].text
#上映时间
year = bs.xpath('//div[@id = "wrapper"]/div/h1/span')[1].text
#电影类型
m_type = []
for t in bs.xpath('//span[@property = "v:genre"]'):
m_type.append(t.text)
a = bs.xpath('//div[@id= "info"]')[0].xpath('string()')
#片长
m_time =a[a.find('片长: ') + 4:a.find('分钟\n')] #时长
#地区
area = a[a.find('制片国家/地区:') + 9:a.find('\n 语言')] #地区
#评分人数
try:
people = bs.xpath('//a[@class = "rating_people"]/span')[0].text
#评分分布
rating = {}
rate_count = bs.xpath('//div[@class = "ratings-on-weight"]/div')
for rate in rate_count:
rating[rate.xpath('span/@title')[0]] = rate.xpath('span[@class = "rating_per"]')[0].text
except:
people = 'None'
rating = {}
#简介
try:
brief = bs.xpath('//span[@property = "v:summary"]')[0].text.strip('\n \u3000\u3000')
except:
brief = 'None'
try:
hot_comment = bs.xpath('//div[@id = "hot-comments"]/div/div/p/span')[0].text
except:
hot_comment = 'None'
cache = pd.DataFrame({'片名':[title],'上映时间':[year],'电影类型':[m_type],'片长':[m_time],
'地区':[area],'评分人数':[people],'评分分布':[rating],'简介':[brief],'热评':[hot_comment],'网址':[url]})
return cache
movie_result = pd.DataFrame()
ip = '' #这里构建自己的IP池
count2 = 1
cw = 1
for url,name in zip(result['网址'].values[6000:],result['片名'].values[6000:]):
#for name,url in wrongs.items():
try:
cache = parse_movie_info(url,headers = headers,ip = ip)
movie_result = pd.concat([movie_result,cache])
#time.sleep(random.random())
print('我们爬取了第:%d部电影-------%s' % (count2,name))
count2 += 1
except:
print('滴滴滴滴滴,第{}次报错'.format(cw))
print('ip is:{}'.format(ip))
cw += 1
time.sleep(2)
continue
电影页面数据爬取结果如下:
数据清洗
#把单列字典的评分分布转化成分开的5列,且每一列是数值型的
def get_rate(x,types):
try:
return float(x[types].strip('%'))
except:
pass
movie_combine['5星'] = movie_combine['format_评分'].apply(get_rate,types = '力荐')
movie_combine['4星'] = movie_combine['format_评分'].apply(get_rate,types = '推荐')
movie_combine['3星'] = movie_combine['format_评分'].apply(get_rate,types = '还行')
movie_combine['2星'] = movie_combine['format_评分'].apply(get_rate,types = '较差')
movie_combine['1星'] = movie_combine['format_评分'].apply(get_rate,types = '很差')
现在我们的数据长这样的:
#按照总评分,5星评分人数占比,4星占比,3星..依次类推
movie_combine.sort_values(['评分','5星','4星','3星','2星','1星'],ascending = False,inplace = True)
但是仔细看排序结果,我们会发现这样排序的一些小瑕疵,一些高分电影其实是比较小众的,比如“剧院魅影:25周年纪念演出”和“悲惨世界:25周年纪念演唱会”等。
基于年代上映量数据,我们从20世纪30年代开始制作排名;
为了避免有些年代电影过少,优化成各年代TOP 10%的电影推荐;
同时,为了避免近年电影过多,每个年代推荐的上限数不超过100部。
final_rank = pd.DataFrame()
for century,count in zip(century_f.index,century_f.values):
f1 = movie_f2.loc[movie_f['年代'] == century,:]
if count < 1000:
return_num = int(count * 0.1)
else:
return_num = 100
f2 = f1.iloc[:return_num,:]
final_rank = pd.concat([final_rank,f2])
根据上一步构造的century_f变量,结合每个年代上映电影量,不足1000部的筛选前10%,超过1000部的只筛选前100部,结果,就呼之而出了。
作者:周志鹏,2年数据分析,深切感受到数据分析的有趣和学习过程中缺少案例的无奈,遂新开公众号「数据不吹牛」,定期更新数据分析相关技巧和有趣案例(含实战数据集),欢迎大家关注交流。
声明:本文为作者投稿,版权归其所有。